Skip to content

fix(BACKEND-ATTN-REGISTRY): CPU_ATTN was a name in a list, so a head size FLASH_ATTN declines left the CPU with no backend (#1371) - #1392

Merged
localai-bot merged 8 commits into
mainfrom
fix/1371-cpu-attn-backend
Aug 20, 2026
Merged

fix(BACKEND-ATTN-REGISTRY): CPU_ATTN was a name in a list, so a head size FLASH_ATTN declines left the CPU with no backend (#1371)#1392
localai-bot merged 8 commits into
mainfrom
fix/1371-cpu-attn-backend

Conversation

@localai-bot

@localai-bot localai-bot commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

CpuPlatform::get_attn_backend_priority has returned {CPU_ATTN, FLASH_ATTN}
since the platform seam landed, mirroring cpu.py:75-87, where CPU_ATTN is the
only answer upstream ever gives on a CPU. Nothing ever registered it. Every CPU
selection walked past the first name and stopped on FLASH_ATTN, and that was
recorded as behavior-preserving, which it was: our CPU paged-attention kernel
reads FlashAttention's NHD layout, so the second name described the CPU's real
KV geometry.

It was behavior-preserving only for as long as FLASH_ATTN accepted everything
the CPU asked of it. #1332 M1 (369ea7fd4) ported validate_configuration, and
with it flash_attn.py:170-178's rule that a head size must be a multiple of 8.
That rule is correct and it is about the FA2 kernel. It is now also, on four
device types, about kernels that are not FA2, because FlashAttentionBackend is
registered by NAME for kCPU, kMETAL, kVULKAN and kTENSTORRENT on the stated
precondition that those kernels share its LAYOUT. A CPU request with head_size 6
therefore matched no registered backend at all and SelectAttentionBackendName
threw out of GPUModelRunner::initialize_kv_cache.

So the fallback was never removed. There was never a fallback: CPU had exactly
one registered dense backend, and it stopped answering. This registers the other
one. CpuAttentionBackend is ported from cpu_attn.py:39-110 @ pin
5559679229, and CPU selection now returns CPU_ATTN for every request, which is
what upstream returns. FLASH_ATTN stays registered for kCPU as the second entry.
The head_size % 8 guard is untouched; a case here asserts FLASH_ATTN still
refuses head_size 6 on CPU, so repairing this by widening the guard turns that
line red.

The capability surface is gated, and the upstream test for it is ported

Six of the seven declarations in the new class were ungated when it first landed.
A review corrupted every one except kName/get_name and get_kv_cache_shape
-- including WIDENING get_supported_kernel_block_sizes from {16} to {1},
which would let CPU_ATTN accept block_size 1 -- and every test binary stayed
green at Status: SUCCESS!. A declaration nothing reads is a claim, and this
registry turns claims into selections.

supported_dtypes is the one that changes a live answer. It adds
vt::DType::kF32, and validate_configuration consumes cfg.dtype through
supports_dtype, so before this change a CPU f32 request was REFUSED with
dtype not supported: FLASH_ATTN declares {f16, bf16} and nothing stood
behind it. Upstream has a test for exactly that, and it was not ported.

It is ported now, from
tests/kernels/attention/test_attention_selector.py:230-241 @ pin
5559679229bc961848b121ccdeaa8fa5d79bec98 -- test_fp32_fallback, cpu arm,
get_attn_backend(16, torch.float32, None).get_name() == "CPU_ATTN" on
CpuPlatform. Carried over field for field through SelectAttentionBackendName,
asserting both halves: FLASH_ATTN's refusal with its single reason string, and
CPU_ATTN validating the identical request with no reasons at all. The cuda and
hip arms are NOT ported and the case says why rather than dropping them in
silence -- this tree registers no FLEX_ATTENTION, and RocmAttentionBackend
declares no head-size list, so the hip arm's expected throw would arrive for the
dtype rather than for upstream's minimum head size of 32.

A second case pins the rest, entry for entry against cpu_attn.py. Each block
asserts the DECLARED list and the predicate the base class derives from it,
because they fail differently: deleting an override moves the list to the base
default, while corrupting entries leaves the shape intact. Widening is covered in
its own direction -- supports_block_size(1) and (8) are asserted FALSE, which
is what the {16} -> {1} corruption trips.

Eight mutations on b8049371, each printing its compile status beside its
verdict. Base: test_attn_backend_registry 20 cases, 125 assertions,
Status: SUCCESS!, rc 0. The head moved to 3853ec5f afterwards for two more
origin/main merges, neither of which touches any of the three files involved --
verified by git diff b8049371..3853ec5f over cpu_attn.h, cpu_attn.cpp and
test_attn_backend_registry.cpp, which is empty.

# Corruption compile_rc Result
M1 supported_dtypes drops kF32 0 2 cases / 5 assertions red
M2 supported_kv_cache_dtypes gains fp8_e5m2 0 1 case / 2 assertions red
M3 get_supported_kernel_block_sizes {16} -> {1} 0 1 case / 5 assertions red
M4 supports_non_causal true -> false 0 1 case / 2 assertions red
M5 supports_sliding_window true -> false 0 1 case / 2 assertions red
M6 supports_attn_type drops encoder_decoder 0 1 case / 1 assertion red
M7 get_supported_head_sizes {} -> upstream's eleven 0 3 cases / 13 assertions red
M8 the production registrar deleted 0 5 cases / 6 assertions red

Every one exits 1 at Status: FAILURE!, and every one restored both files
byte-for-byte, verified by sha256 rather than by git status. M8 is the
reachability re-proof on the repaired head: the class still compiles and still
constructs, and the gate goes red because nothing reaches it. Read its assertion
count against the others -- 71, not 125 -- because five thrown cases run no
assertions at all. That collapse is the second half of #1371's report, reproduced
here on purpose.

Three live comments said the opposite of what the tree does

registry.h's own doc comment on SelectAttentionBackendName promised
"FLASH_ATTN" on CPU. runner.cpp said the same in the selection block.
porting-inventory.md, whose header declares it a living record kept up to date
at all times, still described CPU_ATTN as named-but-unregistered. All three now
state the CPU answer, and all three note that both names report the same NHD
layout, so the geometry did not change with the name.

One more live surface stated the old CPU answer in the present tense:
.agents/specs/extensibility-platform-seam-2026-07-18.md, under its dated
"Item 4 -- LANDED 2026-07-19" heading. Its paragraph is kept exactly as written,
because it records what was true on that date. A superseded marker naming #1371
and this pull request is added beside it, so a grep cannot land a reader on a
false CURRENT answer with nothing to warn them.

parity-ledger.md's 2026-07-19 row is deliberately left alone. It is a dated
entry recording what that landing did, it was true when written, and the ledger
declares itself append-only, one row per commit, newest last. Editing a dated
row in a declared append-only log falsifies provenance rather than correcting
it. That is history; porting-inventory.md, which declares itself kept up to
date at all times, is live coverage, and it was corrected.

There are three deviations from cpu_attn.py, not two

supported_kv_cache_dtypes drops "fp8_e5m2" from upstream's four entries. The
omission is right -- KvKind::kFp8 resolves to LoadKvFp8E4M3 alone and both vt
entry points VT_CHECK-refuse an e5m2 KV cache -- but the header said "TWO
RECORDED DEVIATIONS" and justified this one by claiming FlashAttentionBackend's
list is trimmed for the same reason. It is not trimmed. It is
flash_attn.py:74-80 verbatim, and upstream's FA backend never claimed e5m2
either, so it is no precedent for anything. The header now records three, and the
third rests on our own kernel's single encoding.

That anchor was wrong in the round that introduced it, and it is corrected here.
flash_attn.py:71 is a BLANK line at the pin; supported_dtypes is at :73
and the five-entry supported_kv_cache_dtypes at :74-80. The range offered as
proof the list is VERBATIM stopped at "fp8",, so it did not contain
"fp8_e4m3" or the closing bracket -- it did not contain the list it was
proving. Three sites carried it: cpu_attn.h:70 and two comments in
test_attn_backend_registry.cpp. Each was re-derived at the pin and asserted
UNIQUE in that file, and each now matches the four sites this tree already had
right, among them backend.h:384 on the very declaration under discussion and
test_attn_validate_configuration.cpp:110 in the sibling suite.
scripts/check-symbol-anchors.py cannot catch this: it documents that it
deliberately does not verify line citations, so its green was never coverage
here.

The header also states outright what deviation 2 left implicit: an empty
head-size list is WIDER than upstream's eleven sizes, 0 included. That is our
scalar kernel's genuine generality and the base class's own default, and it is
still a wider claim than the file this one is ported from makes. docs/USAGE.md
now says the user-visible half of the same thing: the CPU serves f32, and refuses
fp8_e5m2 by name -- and, since the page named the CPU's KV-cache list without
ever naming the GPU's, that fp8_e5m2 refusal read as this project's limitation
when it is the reference engine's own. Both lists are stated there now.

docs/FEATURES.md records the backend surface change on the CPU row, in the
shape the ROCm row already uses for the directly analogous ROCM_ATTN
registration at #1056/#1065. Registering a CPU attention backend is a backend
surface change, which is the trigger AGENTS.md names for that page. The
convention is not uniform -- the CUDA row names no attention backend at all --
so this follows the one precedent that exists rather than inventing a shape.

Evidence

CPU-only, no lease, -DVLLM_CPP_CUDA=OFF, on 4c649319 (Debug for the red;
Release for the re-run after the fifth merge).

RED at the pinned base 5f68e60df22670a714f31d6362695b012b2598e2:
test_nemotron_h_paged_forward exits 1 with 12 cases, 2 passed, 10 failed, and
18 assertions. GREEN after: exit 0, 12 of 12 passed, 3256 assertions. The
assertion count is the number to read. A thrown doctest case runs no assertions,
so the failing run printed 0 failed beside ten throws, and any gate matching on
the assertions line saw a clean sweep of eighteen.

Focused gate, all rc 0 and re-run in full at 4c649319:
test_attn_backend_registry 20/20 at 125 assertions,
test_attn_validate_configuration 21/21 at 76, test_platform 14/14 at 114,
test_runner 19/19 at 543, test_nemotron_h_paged_forward 12/12 at 3256. Every
one reports Status: SUCCESS!, which is read beside the assertion count rather
than instead of it.

scripts/agent-preflight.sh --fail-on-skip at 4c649319: All gates green,
rc 0, ZERO skipped and ZERO failed. commit-trailers and commit-style both
RAN and both report ok against origin/main 63d87805c, which is what the five
merges in this branch are for -- while the branch was behind, both gates
reported SKIP, and a skip is not a green: it means neither gate reported
anything about this tree. test_cpu_x86_llamacpp_floor, which had refused to
measure under NO_QUIET_WINDOW ... load=194.85 on earlier runs, passes on a
quiet box; both tests/scripts/test_cpu_x86_llamacpp_floor.py and
scripts/cpu-x86-llamacpp-floor.sh are byte-identical to origin/main here and
this branch touches neither, so its earlier red was this box's load average and
never a verdict about any code.

test_nemotron_h_paged_forward is the test currently failing build-test-cpu on
every open pull request, because it is the test #1371 breaks. It passes here.
test_qwen3_5_decode_graph_seam segfaults on main independently
(#1422) and is untouched by
this branch, so a red build-test-cpu on this pull request has to be read by
WHICH test failed, not by its colour.

Closes #1371.

Metal, Vulkan and Tenstorrent carry the identical inherited rule and are filed as
#1389 rather than repaired here: none has an upstream backend to mirror, so each
needs its declared capabilities authored, and that is a spec. kROCM is unaffected
because RocmAttentionBackend declares no head-size constraint.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]

mudler added 6 commits August 19, 2026 18:23
…size FLASH_ATTN declines left the CPU with no backend (#1371)

`CpuPlatform::get_attn_backend_priority` has returned `{CPU_ATTN, FLASH_ATTN}`
since the platform seam landed, mirroring `cpu.py:75-87`, where CPU_ATTN is the
only answer upstream ever gives on a CPU. Nothing ever registered it. Every CPU
selection walked past the first name and stopped on FLASH_ATTN, and that was
recorded as behavior-preserving, which it was: our CPU paged-attention kernel
reads FlashAttention's NHD layout, so the second name described the CPU's real
KV geometry.

It was behavior-preserving only for as long as FLASH_ATTN accepted everything
the CPU asked of it. #1332 M1 (`369ea7fd4`) ported `validate_configuration`, and
with it `flash_attn.py:170-178`'s rule that a head size must be a multiple of 8.
That rule is correct and it is about the FA2 kernel. It is now also, on four
device types, about kernels that are not FA2, because `FlashAttentionBackend` is
registered by NAME for kCPU, kMETAL, kVULKAN and kTENSTORRENT on the stated
precondition that those kernels share its LAYOUT. A CPU request with head_size 6
therefore matched no registered backend at all and `SelectAttentionBackendName`
threw out of `GPUModelRunner::initialize_kv_cache`.

So the fallback was never removed. There was never a fallback: CPU had exactly
one registered dense backend, and it stopped answering. This registers the other
one. `CpuAttentionBackend` is ported from `cpu_attn.py:39-110` @ pin
`5559679229`, and CPU selection now returns CPU_ATTN for every request, which is
what upstream returns. FLASH_ATTN stays registered for kCPU as the second entry.
The `head_size % 8` guard is untouched; a case added here asserts FLASH_ATTN
still refuses head_size 6 on CPU, so repairing this by widening the guard turns
that line red.

Two deviations from `cpu_attn.py` are recorded in the new header. The KV shape
is the NHD 5-dim one our CPU kernel reads by strides, not upstream's HND, which
is the long-standing deviation `cpu_paged_attn.cpp:5-8` already carries and the
reason CPU could fall through to FLASH_ATTN at all. And the head-size list is
empty, meaning no constraint, where upstream lists eleven sizes: that list
describes upstream's fixed-width vectorized CPU kernel, and ours is a scalar
loop whose only specialization is the K/V cache dtype. Declaring upstream's list
would refuse work this binary demonstrably performs, which is #1332's own defect
pointed the other way.

Evidence, on this tree, CPU-only, no lease. RED at the pinned base
`5f68e60df22670a714f31d6362695b012b2598e2`: `test_nemotron_h_paged_forward`
exits 1 with 12 cases, 2 passed, 10 failed, and 18 assertions. GREEN after: exit
0, 12 of 12 passed, 3256 assertions. The assertion count is the number to read.
A thrown doctest case runs no assertions, so the failing run printed
`0 failed` beside ten throws, and any gate matching on the assertions line saw a
clean sweep of eighteen. Mutation: renaming the registered name to
`MUTANT_NOT_CPU_ATTN` compiles (rc 0), and the same binary returns to exactly the
red shape, 2 passed and 10 failed at 18 assertions. The first attempt at that
mutation failed to compile and the stale binary reported SUCCESS, which is why
the compile status is quoted beside the result.

Metal, Vulkan and Tenstorrent carry the identical inherited rule and are filed as
#1389 rather than fixed here: none has an upstream backend to mirror, so each
needs its declared capabilities authored, and that is a spec. kROCM is unaffected
because `RocmAttentionBackend` declares no head-size constraint.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…iler and style gates stop skipping

FOLLOWING_AGENTS_PROTOCOL

`origin/main` had moved to `96ed8346f`, which is not an ancestor of this branch's
head. `scripts/agent-preflight.sh` reacts to that by SKIPPING `commit-trailers`
and `commit-style` rather than running them against a range it cannot compute, so
the branch was carrying two gates that had never executed on its own commits. A
skip is not a green, and this merge is what turns them back on.

The only textual overlap is `.agents/issue-index.md`, whose `merge=union` driver
appended both sides' rows. Verified after the merge: 443 rows, no duplicated row
key, and this branch's #1371 and #1389 rows are byte-identical to the versions it
authored.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
… port the upstream test for the one arm it widened (#1371)

FOLLOWING_AGENTS_PROTOCOL

Six of the seven declarations in the new `CpuAttentionBackend` were ungated. A
review corrupted every one of them except `kName`/`get_name` and
`get_kv_cache_shape` -- including WIDENING `get_supported_kernel_block_sizes`
from `{16}` to `{1}`, which would let CPU_ATTN accept `block_size` 1 -- and every
test binary stayed green at `Status: SUCCESS!`. A declaration nothing reads is a
claim, and this registry turns claims into selections.

`supported_dtypes` is the one that changes a live answer. It newly adds
`vt::DType::kF32`, and `validate_configuration` consumes `cfg.dtype` through
`supports_dtype`, so before this row a CPU f32 request was REFUSED with `dtype
not supported`: FLASH_ATTN declares `{f16, bf16}` and there was nothing behind
it. Upstream has a test for exactly that, and it was not ported.

So port it. `tests/kernels/attention/test_attention_selector.py:230-241` @ pin
`5559679229bc961848b121ccdeaa8fa5d79bec98`, `test_fp32_fallback`, cpu arm:
`get_attn_backend(16, torch.float32, None).get_name() == "CPU_ATTN"` on
`CpuPlatform`. Carried over field for field through
`SelectAttentionBackendName`, with both halves asserted -- FLASH_ATTN's refusal
and its single reason string, and CPU_ATTN validating the identical request with
no reasons at all. The cuda and hip arms are NOT ported and the case says why
rather than omitting them silently: this tree registers no FLEX_ATTENTION, and
`RocmAttentionBackend` declares no head-size list, so the hip arm's expected
throw would arrive for the dtype instead of for upstream's minimum head size of
32.

A second case pins the rest, entry for entry against `cpu_attn.py`, asserting the
DECLARED list and the predicate the base class derives from it, because those
fail differently: deleting an override moves the list to the base default, while
corrupting entries leaves the shape intact. Both directions are covered --
`supports_block_size(1)` and `(8)` are asserted FALSE, which is what the {16} ->
{1} widening trips.

Three comments that describe CPU selection were left saying the opposite of what
the tree now does. `registry.h`'s own doc comment on `SelectAttentionBackendName`
promised `"FLASH_ATTN"` on CPU; `runner.cpp` said the same in the selection
block; and `porting-inventory.md`, which its header declares a living record kept
up to date at all times, still described CPU_ATTN as named-but-unregistered. All
three now state the CPU answer. `parity-ledger.md`'s 2026-07-19 row is left
ALONE: it is a dated entry describing what that landing did, and it was true.

Finally, there are THREE deviations from `cpu_attn.py`, not two, and the third's
stated precedent does not exist. `supported_kv_cache_dtypes` drops `"fp8_e5m2"`
from upstream's four entries, which is correct -- `KvKind::kFp8` resolves to
`LoadKvFp8E4M3` alone and both vt entry points VT_CHECK-refuse e5m2 -- but the
comment justified it by claiming FlashAttentionBackend's list is trimmed for the
same reason. It is not trimmed. It is `flash_attn.py:71-78` verbatim, and
upstream's FA backend never claimed e5m2 either, so it is no precedent for
anything. The header now records three deviations and rests the third on our own
kernel's single encoding. It also states outright what deviation 2 leaves
implicit: an empty head-size list is WIDER than upstream's eleven sizes, 0
included.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…c while the gate ran

FOLLOWING_AGENTS_PROTOCOL

`origin/main` advanced from `96ed8346f` to `7265cea08` between the first sync and
the preflight run, so `commit-trailers` and `commit-style` reported SKIP a second
time rather than a verdict. This re-merge is what makes them execute.

`.agents/issue-index.md` is the only textual overlap and its `merge=union` driver
appended both sides. Verified after the merge: no duplicated row key, and this
branch's #1371 and #1389 rows are byte-identical to the versions it authored.
`tests/vllm/platforms/test_platform.cpp` took 144 lines from main beside this
branch's own edit to the CPU priority case, and git resolved the two hunks
without overlap.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
FOLLOWING_AGENTS_PROTOCOL

`main` advanced three more times while this branch's gate ran, and GitHub reported
the pull request CONFLICTING again. `git merge-tree --write-tree` reported the
same pair CLEAN, which is the known divergence: the forge does not apply this
repository's `merge=union` driver for `.agents/issue-index.md`, so an append-only
row reads as a conflict there and as no conflict here. An explicit merge commit
settles it either way, and it also puts `origin/main` back in this branch's
ancestry so `commit-trailers` and `commit-style` keep executing instead of
reporting SKIP.

Verified after the merge: `.agents/issue-index.md` has no duplicated row key, and
this branch's #1371 and #1389 rows are byte-identical to the versions it authored.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
FOLLOWING_AGENTS_PROTOCOL

One more commit landed on `main` after the previous sync, and the forge reported
the pull request CONFLICTING again while `git merge-tree --write-tree` reported
the same pair CLEAN. That is the `.agents/issue-index.md` divergence this branch
has now hit four times: GitHub does not apply the repository's `merge=union`
driver, so an append-only row reads as a conflict there and as nothing here.

Verified after the merge: no duplicated row key in the index, and this branch's
#1371 and #1389 rows are byte-identical to the versions it authored. The merged
commit touches model registries and `vt` persistent step input; it changes none
of the three files this branch's mutation evidence was measured over.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot pushed a commit that referenced this pull request Aug 20, 2026
… is real

The new paged-forward case name carried a comma, which doctest's `-tc` splits
on, so a targeted run of it would have reported `0 cases ran` and `SUCCESS!`.
The file's own preamble warns about exactly that. Renamed.

The gate then ran, and the spec now records what it actually printed rather than
what it was expected to print.

`test_nemotron_h_moe_device` builds and exits 0 with 4 cases passed, and ALL
FOUR skip: there is no CUDA device here, so the 4 assertions are the skip
notices and the synthetic numeric gate examined nothing. It stays NEVER RUN.

`test_nemotron_h_paged_forward` exits 1 on this tree with 11 of 13 cases
throwing the identical "No valid attention backend for device type 0 from
{FLASH_ATTN: [head_size not supported]}" at GPUModelRunner construction. That is
#1371, 10 of the 11 are cases this row never touched, and overlaying #1392's
production fix in the working tree -- never committed, reverted afterwards --
turns the same binary green at 13/13 and 3269 assertions. The new case alone
passes 13 assertions. Worth recording: doctest printed `assertions: 18 | 18
passed | 0 failed` while 11 cases were throwing, so the assertion line alone
would have read as a pass.

Red-first, each applied to a scratch copy, proven applied by `git diff --stat`,
built before being run, and restored to an identical sha256. M1 substitutes the
request count for `n_out` at the host projection: compile 0, run 1, FAILURE --
the returned row count is 1 where the gather asked for 3. M2 stops filling
`trace->final_normed`: compile 0, run 1, FAILURE.

M4 restores the exact pre-repair two-download shape and stays GREEN. That is
reported rather than hidden. The duplicate copies the same unchanged buffer and
produces identical bytes, so nothing outside the function can see it; it is
repaired structurally and no assertion pretends to catch it.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
mudler added 2 commits August 20, 2026 03:36
…t is, at flash_attn.py:74-80 (#1371)

The previous repair replaced a false justification with a true one and cited it
wrongly, in three places. `flash_attn.py:71` is a BLANK line at pin
`5559679229bc961848b121ccdeaa8fa5d79bec98`; `supported_dtypes` is at `:73` and
the five-entry `supported_kv_cache_dtypes` at `:74-80`. The range `71-78` that
the comment offered as proof our list is upstream's VERBATIM stops at `"fp8",`
and so does not contain `"fp8_e4m3"` or the closing bracket, which is to say it
does not contain the list it was proving. The substance is unchanged and was
independently confirmed: FLASH_ATTN's list IS upstream's verbatim, upstream's FA
backend never claimed e5m2, and the third recorded deviation therefore rests on
our own kernel's single encoding alone. Only the citation moves. Every corrected
line was re-derived at the pin and asserted UNIQUE in that file, and each now
matches the four sites this tree already had right, including `backend.h:384`
on the very declaration under discussion.

`docs/USAGE.md` gains what that re-derivation is worth to a reader. The page
told a CPU user which KV-cache dtypes the CPU takes and that `fp8_e5m2` is
refused, without ever saying what the GPU takes — so the one refusal a user can
actually hit on either device read as this project's limitation. It is not:
`fp8_e5m2` is outside the reference engine's own FLASH_ATTN list, which is the
list this commit re-derived.

`extensibility-platform-seam-2026-07-18.md` still stated the pre-#1371 CPU
answer in the present tense. Its paragraph is kept exactly as written, because
it is dated 2026-07-19 and records what was true then; a superseded marker is
added beside it so a `grep` cannot land a reader on a false CURRENT answer with
nothing to warn them. `.agents/parity-ledger.md` is deliberately NOT touched:
it declares itself append-only, one dated row per commit, and editing a dated
row there would falsify provenance rather than correct it.

`docs/FEATURES.md` gains the CPU row's backend fact, in the shape the ROCm row
already uses for the directly analogous `ROCM_ATTN` registration. Registering a
CPU attention backend is a backend surface change, which is the trigger
`AGENTS.md` names for that page.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
Four more commits landed on `main` while the final repair round ran, so the
branch was behind again and `scripts/agent-preflight.sh --fail-on-skip` SKIPPED
`commit-trailers` and `commit-style`: neither gate reported anything about this
tree, which is not a green. This merge is what lets them run.

Three files overlap, and all three merged without a conflict.
`.agents/issue-index.md` stays a PURE APPEND against `origin/main` — two rows
added, none removed, no duplicated row key, and this branch's #1371 and #1389
rows are byte-identical by sha256 to the versions it authored.
`CMakeLists.txt` keeps this branch's `cpu_attn.cpp` source entry beside the new
`vllm_bpe_encode_cost` object target. `docs/USAGE.md` keeps this branch's
twenty added lines against `origin/main` while taking the incoming edit.

Nothing else this branch owns is touched. The merged commits change the
block-wise FP8 dense wiring, the BPE cost harness, two record checkers and
several records; the C++ this branch's mutation evidence was measured over is
outside every one of them.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot pushed a commit that referenced this pull request Aug 20, 2026
…d on

The `## Now` evidence tables were headed "a run on this box" and reported the
new §12 case at `run_rc 0 / 1 passed / 13 assertions / SUCCESS!`, with M1 and M2
red beneath it. Every one of those numbers is real, and none of them is a
property of this tree. On the reviewed head with nothing overlaid the §12 case
is ALREADY `run_rc=1`: it throws #1371's `No valid attention backend for device
type 0 from {FLASH_ATTN: [head_size not supported]}` at `GPUModelRunner`
construction, before its first assertion. A mutation cannot be shown to turn a
case red when the case is red without it, so as written the tables asserted the
one thing their own numbers could not support.

Both tables now carry an `overlay` column, state the un-overlaid baseline as its
own row, and say why the overlay is what makes the vehicle able to report a
green at all. Re-derived at `72d736867` rather than restated: §12 alone with no
overlay gives `run_rc=1`, `1 | 0 passed | 1 failed | 12 skipped`, `assertions:
0`; with #1392's production fix overlaid, `run_rc=0`, `1 passed`, `13
assertions`; the whole binary overlaid, `run_rc=0`, 13/13, 3269 assertions; M1
`compile_rc=0 run_rc=1` throwing `gathered row count does not match hidden_size`
at `nemotron_h.cpp:1029`; M2 `compile_rc=0 run_rc=1` on `REQUIRE( 0 == 288 )`.
The overlay and every mutation were reverted to byte-identical sources
(`nemotron_h_device.cpp` back to `sha256 abf6e21f...`).

The Marlin object figure gets the same treatment for the same reason. It was
quoted as 1 269 696 bytes with no commit beside it. That number is not wrong --
it reproduces exactly at `806b263e7`, `8fa900a62`, `1c62d9974` and at the
reviewed head `29b1128e3` -- but the translation unit moved twice afterwards,
to 1 272 808 at `fedf78d86` and to 1 268 552 from `bff2b7b2f` on, and an
evidence block that names no SHA cannot tell a reader which of the three it
means. The measured series is now in the file, the head value is anchored to
`72d736867`, and compilation is shown deterministic here (two runs, identical
`sha256 42d670b6...`, gcc 13.3.0, no `nvcc`).

Also names the process-wide static this row does NOT decline.
`dense_nvfp4::DenseMarlinWorkspace` (`dense_nvfp4_gemm.h:506`) is a
process-static device allocation keyed on nothing, shared by every caller of
`MatmulNvfp4MarlinD` and `GateUpFusedMarlinD`, and NemotronH's `lm_head` becomes
its fourth consumer. It is pre-existing -- `80d1da096`, and the definition is
byte-identical at this row's merge base and at its head -- and nothing here
addresses it. In a row whose thesis is refusing to inherit the sibling
process-static defect (#984), staying silent about the one it does inherit was
an asymmetry worth one paragraph. The `fallback_gemms` armed-instrument anchor
is corrected from `test_qwen3_forward.cpp:497`, the `TEST_CASE(` line, to `:559`,
the assertion.

No product code changes.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
@localai-bot
localai-bot merged commit 9ecaf1b into main Aug 20, 2026
25 of 27 checks passed
@localai-bot
localai-bot deleted the fix/1371-cpu-attn-backend branch August 20, 2026 07:26
localai-bot pushed a commit that referenced this pull request Aug 20, 2026
… red this branch inherited

#1392 merged at 07:26Z and closed #1371, so `CpuAttentionBackend` is registered
and `cpu_attn.cpp` is on `main`. This branch was gating against a `main` without
that fix, and #1409 measured what that costs: `build-test-cpu` and BOTH
`sanitize-cpu` jobs failed on the identical test, `175 -
test_nemotron_h_paged_forward`, against a diff of zero code files. Picking up
this `main` is what lets those three jobs report on THIS change rather than on
#1371, so it is taken before the CI verdict is read rather than after.

Whether the three clear is a prediction until the run says so. If a `sanitize-cpu`
job still fails, the failing test gets named rather than the job being waved
through as a known red.

The keyed record is verified BY KEY, not by a clean automatic merge.
`.agents/issue-index.md`: 461 rows on `origin/main`, 465 merged, zero duplicated
keys, zero lost keys, every `origin/main` row byte-for-byte equal, `origin/main`'s
order preserved as a subsequence, and the four branch-only rows (#926, #1290,
#1416, #1431) byte-for-byte equal to the branch versions. The append stays an
append.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot pushed a commit that referenced this pull request Aug 20, 2026
…ce was reddening this branch (#1395)

FOLLOWING_AGENTS_PROTOCOL

`build-test-cpu` and both `sanitize-cpu` jobs were red here on one test,
`175 - test_nemotron_h_paged_forward`, and the defect was never in this branch.
#1371 left `CPU_ATTN` as a name in a candidate list with no registrar, so the
CPU inherited FlashAttention's `head_size % 8` constraint with no fallback.
`9ecaf1bb3` (#1392) fixes that. This merge is taken so the gates judge the
branch against the tree it will land on, and so `commit-trailers` and
`commit-style` keep RUNNING instead of skipping on a branch that is behind.

The merged commit is `f07f96e1c` (#1444) rather than `9ecaf1bb3`, because
`origin/main` advanced by one documentation commit while this merge was being
prepared. `9ecaf1bb3` is an ancestor of it, so the fix this merge is taken for
is present either way. Merging the older SHA would have left the branch behind
again and skipped the same two gates a second time.

`.agents/issue-index.md` is the one keyed record both sides touch, and it was
verified by key rather than trusted to the automatic merge: `origin/main`
carries 462 rows and this tree carries 463, all 462 of main's rows are present
byte-identical and in main's own order, the preamble is byte-identical, and the
one added row is this branch's own `#1395`. Nothing is removed and no key is
duplicated.

No spec, record or product file is touched beyond what the merge itself
requires.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot pushed a commit that referenced this pull request Aug 20, 2026
…ce was reddening this branch (#1313)

FOLLOWING_AGENTS_PROTOCOL

`build-test-cpu` and both `sanitize-cpu` jobs were red here on one test,
`175 - test_nemotron_h_paged_forward`, and the defect was never in this branch.
#1371 left `CPU_ATTN` as a name in a candidate list with no registrar, so the
CPU inherited FlashAttention's `head_size % 8` constraint with no fallback.
`9ecaf1bb3` (#1392) fixes that, and `f07f96e1c` (#1444) is the current
`origin/main` that carries it. Merging the older SHA would have left this branch
behind again, and `commit-trailers` and `commit-style` SKIP on a branch that is
behind. A skip is not a green, which is the state this branch was already in.

Four files are touched by both sides, and each was verified rather than trusted
to the automatic merge. `.agents/issue-index.md` is the keyed record: main
carries 462 rows and this tree carries 463, all 462 of main's rows are present
byte-identical and in main's own order, the preamble is byte-identical, and the
one added row is this branch's own `#1313`. Nothing is removed and no key is
duplicated. For `docs/USAGE.md`, `src/vllm/v1/worker/gpu/runner.cpp` and
`tests/CMakeLists.txt` the two sides edit disjoint regions, and that is measured
rather than asserted: the added and removed content lines of `merged vs main`
hash equal to this branch's own delta, and those of `merged vs branch` hash
equal to main's own delta, for all three files.

No file is touched beyond what the merge itself requires.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot pushed a commit that referenced this pull request Aug 20, 2026
…ce was reddening this branch (#1074)

FOLLOWING_AGENTS_PROTOCOL

`build-test-cpu` and both `sanitize-cpu` jobs were red here on one test,
`175 - test_nemotron_h_paged_forward`, and the defect was never in this branch,
which carries no product code at all. #1371 left `CPU_ATTN` as a name in a
candidate list with no registrar, so the CPU inherited FlashAttention's
`head_size % 8` constraint with no fallback. `9ecaf1bb3` (#1392) fixes that, and
`f07f96e1c` (#1444) is the current `origin/main` that carries it. Merging the
older SHA would have left this branch behind again, and `commit-trailers` and
`commit-style` SKIP on a branch that is behind.

Four files are touched by both sides, and each was verified rather than trusted
to the automatic merge. `.agents/issue-index.md` is the keyed record: main
carries 462 rows and this tree carries 464, all 462 of main's rows are present
byte-identical and in main's own order, the preamble is byte-identical, and the
two added rows are this branch's own `#1074` and `#1080`. Nothing is removed and
no key is duplicated. `docs/BENCHMARKS.md`, `docs/FEATURES.md` and
`docs/STATUS.md` are projections whose two sides edit different rows, and that
is measured rather than asserted: the added and removed content lines of
`merged vs main` hash equal to this branch's own delta, and those of
`merged vs branch` hash equal to main's own delta, for all three files.

No file is touched beyond what the merge itself requires.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot pushed a commit that referenced this pull request Aug 21, 2026
…nherited CPU red

The three red CPU jobs on this pull request -- build-test-cpu and both
sanitize-cpu arms -- all failed on `175 - test_nemotron_h_paged_forward`,
which is #1371: CPU_ATTN was a name in a list, so a head size FLASH_ATTN
declines left the CPU with no backend. #1392 fixed that on main. This merge
takes it so the row's own gate result is readable.

Six files auto-merged: `.agents/benchmark-record.md`, `.agents/issue-index.md`
and the four `docs/` projections. Each was verified by key rather than by the
merge exit status -- the branch delta across the merge is byte-identical to the
branch delta from the merge base for all six, and the same holds in the other
direction for main's delta.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [ClaudeCode]
localai-bot pushed a commit that referenced this pull request Aug 21, 2026
…e reviews, and it was red

`device-leakage` never completed while this row was under review, so its
verdict was an input to none of the three passes. It completed after the third
and failed at `vt_ifdef` 35 against a baseline of 32. The spec now records what
the three references were, how each was located, which two were removed and why
the third takes the checker's documented `DSR-ALLOW` escape hatch rather than a
baseline change.

Two evidence tables carry the repair: the before/after counts with their real
exit codes on the CPU configuration that exercises the removals, and a mutation
table showing each of the three repairs is individually load-bearing -- revert
any one alone and the gate reds at 33.

It also retires the `#1392` overlay. The two older tables in this section say
their greens were taken with a fix applied to the working tree and reverted;
that fix has landed on `main` and this branch has merged it, so the tree now
carries it as a committed object. The historical rows keep their `overlay`
cells, because they describe the tree they were measured on.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [ClaudeCode]
localai-bot pushed a commit that referenced this pull request Aug 22, 2026
…ce was reddening this branch (#1074)

FOLLOWING_AGENTS_PROTOCOL

`build-test-cpu` and both `sanitize-cpu` jobs were red here on one test,
`175 - test_nemotron_h_paged_forward`, and the defect was never in this branch,
which carries no product code at all. #1371 left `CPU_ATTN` as a name in a
candidate list with no registrar, so the CPU inherited FlashAttention's
`head_size % 8` constraint with no fallback. `9ecaf1bb3` (#1392) fixes that, and
`f07f96e1c` (#1444) is the current `origin/main` that carries it. Merging the
older SHA would have left this branch behind again, and `commit-trailers` and
`commit-style` SKIP on a branch that is behind.

Four files are touched by both sides, and each was verified rather than trusted
to the automatic merge. `.agents/issue-index.md` is the keyed record: main
carries 462 rows and this tree carries 464, all 462 of main's rows are present
byte-identical and in main's own order, the preamble is byte-identical, and the
two added rows are this branch's own `#1074` and `#1080`. Nothing is removed and
no key is duplicated. `docs/BENCHMARKS.md`, `docs/FEATURES.md` and
`docs/STATUS.md` are projections whose two sides edit different rows, and that
is measured rather than asserted: the added and removed content lines of
`merged vs main` hash equal to this branch's own delta, and those of
`merged vs branch` hash equal to main's own delta, for all three files.

No file is touched beyond what the merge itself requires.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot added a commit that referenced this pull request Aug 22, 2026
… handing out an address-keyed cache (#1415)

Row `A2-Q2b`, issue
[#810](#810), spec
`.agents/specs/nemotron-h-a2q2b-realckpt-lmhead.md`.

Measured first, built second. The row's premise was that NemotronH
decode re-expands quantized weights on the host and that `lm_head` is a
large share of it. That was arithmetic, and the brief that dispatched
this row said so. It is now a count.

**This description was rewritten after a fresh review returned
FINDINGS.** The measurement and the seam design below survived review
unchanged and were verified independently. Three claims that were in the
previous description did not, and they are corrected in place rather
than quietly dropped: the new test had never compiled, the routing
checker was said to hold a branch it does not hold, and the Marlin arm
was said to be unbuildable here when it builds in seconds.

**A scoped re-review then found no correctness defect in the shipped
code and verified all seven repairs, and a final round corrected what it
did find — all of it evidence, records and process.** The evidence
tables named a tree they were not measured on and now carry an `overlay`
column with the un-overlaid baseline stated as its own row; the Marlin
object figure is re-measured and anchored to a SHA; the shared
process-static this row inherits is named; the #1421 index row's counts
are corrected while the pull request is still unmerged and the row still
editable; and `§4` of the spec stopped telling the next agent to take
`flock` on a fleet device. No product code changed in that round.

## What the measurement says

Tallied at `NemotronHOwned::DenseBf16` (`nemotron_h.cpp:395`), the
single seam every host dequant in this architecture passes through, on
the real 21 GB checkpoint (revision `29f2d174`) through the production
ABI driver `examples/nemotron_h_gen`. One decode step, T=1, `top_k` 6,
23 MoE layers, 369 dequant calls:

| group | shape | calls | elements | % |
|---|---|---|---|---|
| routed expert `up_proj` | `[1856, 2688]` | 138 | 688 472 064 | 22.36%
|
| routed expert `down_proj` | `[2688, 1856]` | 138 | 688 472 064 |
22.36% |
| shared expert `down_proj` | `[2688, 3712]` | 23 | 229 490 688 | 7.45%
|
| shared expert `up_proj` | `[3712, 2688]` | 23 | 229 490 688 | 7.45% |
| `lm_head` | `[131072, 2688]` | 1 | 352 321 536 | 11.44% |
| mamba `out_proj` (FP8) | `[2688, 4096]` | 23 | 253 231 104 | 8.23% |
| mamba `in_proj` (FP8) | `[10304, 2688]` | 23 | 637 034 496 | 20.69% |
| **TOTAL** | | **369** | **3 078 512 640** | **100%** |

`138 == 6 x 23` exactly, which is what identifies this as the decode
shape rather than a prefill aggregate.

Three findings, and the first two are corrections:

1. The dispatching estimate put `lm_head` at 131072 x 4096 = 537e6
elements and ~43% of the population. `hidden_size` is 2688. It is 352
321 536 elements and 28.35%.
2. The widely quoted "1.24e9 elements / 2.49 GB per token" names a
REGIME, not a total. It is not the host arm's 3 078 512 640. It is
`mamba + lm_head` = 1 242 587 136 = 2.485 GB, the residue after A2-Q2a
moved the MoE arm to the device, matching to four significant figures.
3. `lm_head` is the last one. Against the A2-D1 discriminator on
`dgx:gpu0` (device mamba ON 1.554 s/token and 108.2x vs vLLM, OFF 10.319
s/token and 718.1x), the mamba arm is worth 6.64x and `lm_head` is on
the host in every leg. Once the mamba arm lands, `lm_head` is 352 321
536 of 352 321 536, i.e. everything that remains. It is also the largest
single re-expansion in the model by 12.7x, so its 704.6 MB transient
bf16 buffer is the one that matters on a unified-memory box that reboots
rather than OOM-kills.

So the estimate's share was wrong, its direction was right, and the case
is stronger after the discriminator than before it.

## The residency decision, taken explicitly

The spec's `## 5. Owed` required this row to choose rather than default:
"A2-Q2a routed around it by never calling either
`MarlinDenseResidentFor`; `lm_head` must do the same or say why not."
That accessor keys its repack cache on the weight's ADDRESS
([#984](#984)), and NemotronH
is exactly the second-consumer condition an address key cannot survive.

Rather than fork the seam as `qwen3_5.cpp` did, or inherit the defect as
routing through it plainly would, the seam is EXTENDED:
`MatmulNvfp4W4A16D` and `MatmulNvfp4MarlinD` accept a caller-owned
`MarlinDenseResident*`, defaulting to the existing cache so every
current caller is byte-for-byte unchanged. NemotronH owns its resident
in a `ResidentSlot` on the weights, which is the property
[#237](#237) added
`ResidentSlot` for and the same call A2-Q2a made for the MoE arena.
[#984](#984) is left exactly as
it was for every other caller, where its own two-engine red-before
belongs.

## What moved

`NemotronHPagedForward`, the production path, now returns a
device-resident `ForwardLogits` on an NVFP4 checkpoint, which is every
released one, and downloads `final_normed` only when a consumer needs
it.

`NemotronHDeviceForward` deliberately keeps the host projection. It is
the operand the numeric gate compares against, it is what makes A2-R's
token gate attributable, and it has no production caller. The spec
required this row to disclose where that property ends: it ends in the
paged forward.

## The allowlist entry is narrowed, not removed

Its one clause is met. But `check-runner-routing-consistency.py`
resolves a cross-TU delegate only for the `Class::ForwardDevice` shape,
and NemotronH's device forward is a free function in another TU, so the
model still classifies HOST although `NemotronHPagedForward` assigns
both `fl.device_tensor` and `fl.device_storage`. Filed as
[#1410](#1410) and left to its
own row: it changes checker semantics, which `AGENTS.md` routes away
from an in-flow fix, and widening a classification to turn a red gate
green is precisely the move that section slows down.

## The review repair

### The new test had never compiled, so the red it claimed could not
have existed

`tests/vllm/models/test_nemotron_h_moe_device.cpp` used
`NemotronHHostWeights` unqualified and never declared it. Measured on
the same command:

| tree | `compile_rc` | errors |
|---|---|---|
| PR head `29b1128e`, this file | 1 | 9 |
| merge base, same file | 0 | 0 |
| repaired, default arm | 0 | 0 |
| repaired, `-DVT_MARLIN_NVFP4=1` | 0 | 0 |

The consequence is larger than the declaration. The file had never
compiled on any platform, so **the red-first result this row claimed for
its synthetic numeric gate did not exist and could not have existed**.
That gate is CUDA-only and still has never executed; the spec now
records `NEVER RUN` rather than restating a red nobody saw. The test
target is deliberately registered with no CUDA guard, and that is what
surfaced this — a case that skips at run time still has to parse on
every CPU build.

### The production source asserted a protection that does not exist

A comment at the device branch claimed the allowlist entry was removed
and that "the routing checker, not a comment, is what now holds this
branch in place: put the host projection back and it goes red by name".
All three claims were false, and they contradicted this description and
the allowlist file in the same commit. Reproduced here on the repaired
tree: baseline `check-runner-routing-consistency.py` exits 0 with "3
host-logits off-framework (3 allowlisted)"; after deleting the whole `if
(DeviceLmHeadEligible(...)) { ... return fl; }` block (`git diff` moves
by 1 insertion and 14 deletions, so it applied) the checker exits 0 with
**byte-identical output**; the file was restored to an identical sha256.

**Nothing automated holds that branch.** The checker is not widened to
make it — that changes checker semantics and
[#1410](#1410) owns it with its
own red-before. The comment now says what is true, the allowlist entry's
own text is corrected the same way, and the reachability deletion
mutation is recorded PENDING a CUDA window rather than claimed. Every
case that can enter the device branch is `MarlinW4A16Selects`-gated and
therefore CUDA-only, so that mutation cannot run on a CPU box.

### A silent dequant fallback that no token gate can see

`DeviceLmHeadEligible` restated the shared dispatcher's three selection
clauses and dropped `MarlinW4A16Enabled()`. Under an explicit
`VT_NVFP4_MARLIN=0` the model said eligible while `MatmulNvfp4W4A16D`
took its naive redundant-dequant arm — the same logits, while
re-uploading the whole `[131072, 2688]` operand on **every decode
step**, because `LmHeadNvfp4View` hands out a stack temporary that
`ResidentNvfp4`'s weight-keyed cache can never hit.

The repair is structural rather than a patched clause. The three clauses
now live once, in `dense_nvfp4::MarlinW4A16Selects`, and both the
dispatcher and the model call it, so they cannot drift. `DeviceLmHeadD`
additionally refuses BY NAME if that same predicate is false on the
operand it is about to hand over, which catches an eligibility answer
taken against a different queue or dtype. That is deliberately the
predicate and not the seam's `fallback_gemms` counter:
`MutableW4A16Stats()` is a plain non-atomic process-wide static, so a
counter window in production would refuse a correct run whenever
anything else took a fallback GEMM concurrently, and a false refusal is
worse than the silence it replaces. The counter is the right instrument
in a test, where single-threadedness is a property of the harness, and
the synthetic gate asserts it there. It is demonstrably armed rather
than assumed: `test_qwen3_forward.cpp:559` — the assertion itself, not
the `TEST_CASE(` line at `:497` — already asserts on CPU that it reaches
exactly `5 * num_hidden_layers` when the dispatcher does fall back.

**And the counter is not the only process-wide static on this route, so
this description should say what the row does NOT decline.**
`dense_nvfp4::DenseMarlinWorkspace` (`dense_nvfp4_gemm.h:506`) is a
process-static DEVICE allocation — `static void* ws` behind a `static
std::mutex`, sized from `MarlinDeviceSms` and keyed on nothing, not even
the device index — shared by every caller of `MatmulNvfp4MarlinD`
(`:539`) and `GateUpFusedMarlinD` (`:700`). It has three consumers today
(the shared dense route in `dense_attn_block.h`, MiniMax-H3, and the
compressed-tensors NVFP4 scheme); NemotronH's `lm_head` becomes a
fourth. It is PRE-EXISTING — it arrived at `80d1da096` and its
definition is byte-identical at this row's merge base and at its head —
and nothing here addresses it. In a change whose thesis is refusing to
inherit the sibling process-static defect
([#984](#984)), saying nothing
about the one it does inherit was an asymmetry a reviewer is entitled to
see stated. The cost the fallback would carry is 198.18 MB per call —
176.16 MB of packed codes plus 22.02 MB of group scales — arithmetic
that checks to the byte against the `[131072, 2688]` geometry. The
behavioural red for this class needs CUDA and is PENDING with the rest.

### The Marlin arm builds here, and saying otherwise overstated the
blocker

The previous description said "this branch has not built the Marlin path
at all". `include/vt/cuda/marlin_repack.h` includes only `<cstdint>`,
`<cstddef>` and `<vector>`, so the host side needs no CUDA toolkit. On
this box, which has no `nvcc`:

```
c++ -std=c++20 -I include -I src -isystem third_party -DVT_MARLIN_NVFP4=1 \
    -Wall -Wextra -Werror -c -o nhd_marlin.o \
    src/vllm/model_executor/models/nemotron_h_device.cpp
-> rc 0, 0 errors, 0 warnings, a 1 268 552-byte object
```

**That byte count is anchored to the merge commit on this branch**, gcc
13.3.0, no `nvcc`, and compilation is deterministic here — two runs of
the identical command, identical `sha256 42d670b6...`. The anchor is the
correction. The figure this description carried until now, 1 269 696,
was accurate when written and accurate at the reviewed head `29b1128e3`,
but the translation unit moved twice afterwards — 1 272 808 at
`fedf78d86`, 1 268 552 from `bff2b7b2f` on — and an evidence block
naming no SHA cannot tell a reader which of the three it means. The
measured series across seven commits is in the spec.

The size is incidental either way; what the block asserts is `rc 0`,
zero errors and zero warnings on a toolchain with no CUDA, and that
holds at every one of those commits. Both arms are compiled and both are
compiled `-Werror`. What genuinely needs `nvcc` is the Marlin **kernel**
and every **execution** of the device path; those stay PENDING and are
what the table below records.

### Reachability, and a CPU vehicle that already existed

Both synthetic cases build `NemotronHHostWeights` by hand, which proves
the class works and not that anything reaches it, and the deletion
mutation for the device arm needs CUDA. But this row also refactored the
part of `NemotronHPagedForward` that every CPU step runs, and
`tests/vllm/models/test_nemotron_h_paged_forward.cpp` already drives
that function through a real `GPUModelRunner`. It gains a case: three
gathered rows against one request, so the returned row count must follow
`logits_indices` and never `num_reqs`; the trace's copy of
`final_normed` fed back through the production host entry point must
reproduce the returned logits bit for bit; and the same logits must come
back with the trace off.

With `trace->capture` set, `DownloadF32(final_normed)` ran twice on the
host arm. One download now serves both consumers on every path. That
duplicate is not observable from outside the function — both copies are
of the same unchanged buffer — so it is repaired structurally and no
assertion pretends to catch it.

### Records

`spec §4.3` did not exist; the requirement is under `## 5. Owed`. The
allowlist's `## Gates` did not either; the heading is `## 3. The gate`.
`docs/FEATURES.md` still listed the device `lm_head` as owed while
`docs/USAGE.md` said it was device; both now say implemented and never
executed, which is the honest state. The appended issue-index row for
[#1410](#1410) cited `## Owed`
where the heading is `## 5. Owed` — cosmetic, since the row passes on
its owning-row disjunct, but the index is append-only and this is the
only window to correct it.

[#1421](#1421) is filed rather
than fixed in flow: two NVFP4 W4A16 upstream anchors are wrong at the
pin, verified against a checkout of `vllm-project/vllm` at
`5559679229bc961848b121ccdeaa8fa5d79bec98`.
`kernels/linear/__init__.py:879-881` is the tail of
`init_wfp8_a16_linear_kernel`, an FP8 helper sharing the `a16`
vocabulary, and the forced-Marlin line it quotes is at 922-924;
`qwen3.py:271-274` is the `class Qwen3ForCausalLM(...)` line and
`packed_modules_mapping` is at 275-278. Both predate the merge base, and
both are cited from nine code and test sites over SEVEN files plus eight
record sites over FIVE record files (`parity-ledger.md` carries three).
The index row said "six files" while its own parenthetical enumerated
seven, and "six record files" where there are five;
`.agents/issue-index.md` is append-only and a landed row can never be
edited, so an unmerged pull request is the only window to correct that,
and this one does. The set still needs re-deriving at the pin rather
than a sed. This branch relocates one of those citations and
deliberately leaves its VALUE unchanged, so the issue owns every site
uniformly instead of half of them looking reviewed.

## Gate status, stated as pending rather than as passed

Every number here is from a run on a CPU box with no CUDA toolkit,
`RelWithDebInfo`, `-Wall -Wextra -Werror`.

**Read the `overlay` column first.** This tree cannot construct a
`GPUModelRunner` at all —
[#1371](#1371) throws there —
so every green below was taken with
[#1392](#1392 production fix
applied to the working tree, never committed here and reverted
byte-for-byte afterwards. The previous version of this table said "a run
on this tree" and reported the §12 case at `run_rc 0 / SUCCESS!`, which
is false of the tree it named: un-overlaid, that case is already red.

| binary | overlay | `run_rc` | cases | assertions | verdict |
|---|---|---|---|---|---|
| `test_nemotron_h_moe_device` | none | 0 | 4, 4 passed | 4 | `SUCCESS!`
— and **all four SKIP**, both A2-Q2b cases included |
| `test_nemotron_h_paged_forward`, whole binary | **none** | 1 | 13, 2
passed, 11 failed | 18 | `FAILURE!` —
[#1371](#1371), not this row |
| `test_nemotron_h_paged_forward`, whole binary | **#1392** | 0 | 13,
**13 passed** | 3269 | `SUCCESS!` |
| the new §12 case alone | **none** | **1** | 1, **0 passed, 1 failed**,
12 skipped | **0** | **`FAILURE!` — throws #1371 before its first
assertion** |
| the new §12 case alone | **#1392** | 0 | 1, 1 passed, 12 skipped | 13
| `SUCCESS!` |

**The `moe_device` row is not a pass, and it is the honest state of the
synthetic gate.** The binary builds and exits 0, but every case takes
the `TryCudaQueue` skip on a GPU-less box, so those 4 assertions are the
skip notices themselves. The numeric gate examined nothing.

**The paged-forward red is
[#1371](#1371 All 11
failures throw the identical `No valid attention backend for device type
0 from {FLASH_ATTN: [head_size not supported]}` at `GPUModelRunner`
construction, and 10 of them are cases this row never touched.
Overlaying [#1392](#1392
production fix in the working tree — never committed here, reverted
afterwards — turns the same binary green. Worth recording: doctest
printed `assertions: 18 | 18 passed | 0 failed` while 11 cases were
throwing, so the assertion line alone would have read as a pass.

### The red-first, on the cases that can run

Each mutation went into a scratch copy of `nemotron_h_device.cpp`, was
proven applied by `git diff --stat`, was built before being run (a
mutation that fails to build proves nothing), and was restored to an
identical sha256.

**Every row below was also run with
[#1392](#1392) overlaid**, and
that is not a detail. Un-overlaid, the §12 case is already `run_rc=1` on
the UNMUTATED tree, so an M1 or M2 red measured there would prove
nothing whatever. The overlay is what lets the vehicle report a green in
the first place; only then can a mutation take it away.

| mutation | overlay | applied | `compile_rc` | `run_rc` | verdict |
|---|---|---|---|---|---|
| — (unmutated control) | **#1392** | — | 0 | 0 | **GREEN**, `SUCCESS!`,
13 assertions |
| — (unmutated control) | **none** | — | 0 | **1** | already **RED** —
#1371, which is why the rest is overlaid |
| **M1** — `n_out` -> `R` at the host projection | **#1392** | 1 ins / 1
del | 0 | **1** | **RED**, `FAILURE!` |
| **M2** — never fill `trace->final_normed` | **#1392** | 1 ins / 2 del
| 0 | **1** | **RED**, `FAILURE!` |
| **M4** — restore the exact pre-repair two-download shape | **#1392** |
3 ins / 8 del | 0 | 0 | **GREEN — reported, not hidden** |

Re-derived rather than restated, on a clean tree at the merge commit,
overlay and mutations reverted to byte-identical sources
(`nemotron_h_device.cpp` back to `sha256 abf6e21f...`): §12 alone
un-overlaid gives `run_rc=1`, `1 | 0 passed | 1 failed | 12 skipped`,
`assertions: 0`, `THREW exception: No valid attention backend for device
type 0 from {FLASH_ATTN: [head_size not supported]}`; overlaid,
`run_rc=0`, 1 passed, 13 assertions; the whole binary overlaid, 13/13,
3269 assertions; M1 `compile_rc=0 run_rc=1` throwing `gathered row count
does not match hidden_size` at `nemotron_h.cpp:1029`; M2 `compile_rc=0
run_rc=1` on `REQUIRE( 0 == 288 )`. M4's cell needs no separate
attestation — un-overlaid every run of this case is red, so a GREEN is
only reachable with the overlay and its verdict entails the column.

M1 is the red the `n_out` rename exists for: with the request count
substituted, the returned row count is 1 where the gather asked for 3.
M2 arms the trace-operand assertion. Both showed the same trap — doctest
printed `assertions: 2 | 2 passed | 0 failed` on M1 while the case was
failing, because a `REQUIRE` throws rather than counting.

M4 staying green is a result, not a gap. The duplicate `DownloadF32`
copies the same unchanged buffer twice and produces identical bytes, so
nothing observable from outside the function can distinguish it. It is
repaired structurally and no assertion pretends to catch it.

**The device arm's own red-first does not exist on a CPU box and is not
claimed.** `MarlinW4A16Selects` is false on a CPU queue, so the device
branch, the `fallback_gemms` assertion and the reachability deletion
mutation are all unreachable here.

### A gate this row was already failing, and cannot repair in place

`scripts/check-doc-checkpoint.py` is red on this branch, and it was red
at the reviewed head `29b1128e` with the identical two errors (`--base
96ed834 --head 29b1128`):

```
ERROR: commit 1c62d99: changed user_usage but did not update docs/USAGE.md
ERROR: commit 8fa900a: changed .agents/benchmark-record.md: measurement
       recorded but did not update docs/STATUS.md
```

The previous description said this gate had one real failure and that it
was repaired. It was not, and `.github/workflows/ci.yml:519` runs the
same `--base/--head` invocation, so the lane is red for this reason
independently of
[#1371](#1371).

The second error names a real gap and it is now closed: this row moves a
lifecycle state and records a measurement, and `docs/STATUS.md` said
neither. It does now, in 192 characters, because that page carries a
shrink-only ratchet on oversized cells and the first attempt took it
from 44 to 45.

The first error, and the historical form of the second, cannot be closed
by a later commit. The checker iterates `commits_in_range` and judges
each commit on its own contents, so the obligation belongs to
`1c62d9974` and `8fa900a62`. Discharging it means rewriting commits that
are already the reviewed base, which resets this pull request's CI
approval and moves the head a fresh reviewer was asked to look at.
**That is a scheduling decision, so it is recorded as owed and raised
for the operator rather than taken by a repair pass.** Run over this
repair's own commits (`origin/main..HEAD`), the checker reports these
two and nothing else.

### The spec sent the next GPU window through the wrong mutex

`§4` step 1 read "Take `$GPU_LOCK` with a blocking `flock` and wait".
`AGENTS.md` requires a fleet device to be claimed through `rc`, and
`dgx:gpu0` is one. The text predates that rule, but this branch is the
wrong place to leave it standing: it ADDS
`scripts/nemotron-h-a2q2b-gpu-gate.sh` for exactly that window, the
script correctly takes no mutex of its own, and the contradicting
instruction sat beside it in the same file — and every PENDING leg above
is waiting on that window, so the next reader of `§4` is the person
about to open it. Two mutexes that do not exclude each other are worse
than one: the fleet cannot see `$GPU_LOCK`, so a `flock` over `ssh` does
not exclude a concurrent `rc` holder and the controller reports the box
free while somebody is on it. On 2026-08-17 that pair voided a whole
speed axis (`minimax-music3.md` §13.10). Step 1 now claims through `rc
run -d dgx:gpu0 --max-runtime <N>h --` with the gate script as the
payload, and step 2's headroom check moves inside the lease and names
the script's own PRECONDITION 1.

### The trailer gates had stopped running

At the previous head, `origin/main` was not an ancestor, so
`scripts/agent-preflight.sh --fail-on-skip` took its `TRAILER_BEHIND`
arm and SKIPPED both `commit-trailers` and `commit-style`. A skip is not
a green. **It then happened five more times during the device-leakage
repair**, because `origin/main` moved under it at `9ecaf1bb3`,
`f07f96e1c`, `aeba0de6f`, `01854663c` and `c8d926ea8`. The ref only
moves on a fetch, so the fix is ordering: merge, then do not fetch
again, then gate, then push. At the pushed head both gates RUN:
`BASE_SHA=c8d926ea8` non-empty, `ANCESTRY_STATUS=0`, `RANGE_COUNT=22`,
`RANGE_STATUS=0`, **zero `SKIP` tokens anywhere in the report**, 85
gates `ok`, and the only failure is `doc-checkpoint range` with the two
commits above. The skip arms are falsified individually rather than read
off a green line, because `agent-preflight.sh` prints a summary only on
failure. Each is armed over exactly this range, proven by a detached
scratch commit with a period-terminated subject and no trailer block,
which turns `check-commit-style.py` and `check-commit-trailers.py` red
with the specific messages.

Both files that both sides of the merge touched are records, resolved by
shape rather than by whatever the three-way merge produced.
`.agents/issue-index.md` is a genuine append-only log carrying
`merge=union`, and both sides append at the tail, so all four new rows
survive — verified additive, 469 + 2 lines on each side to 471, no line
removed or altered. `docs/USAGE.md` is a keyed record: the merged file
is byte-identical to `origin/main`'s version with this branch's scoped
edit re-applied, so every key neither side owns is unchanged.

### The DSR ratchet was red, and no review round ever saw it run

`device-leakage` never COMPLETED while this pull request was under
review, so its verdict was an input to none of the three passes. It
completed after the third and failed: `vt_ifdef` **35 against a baseline
of 32**, `rc 1`. Three `#ifdef VT_MARLIN_NVFP4` sites had been added to
the device-agnostic shared layer, which is exactly what the ratchet
exists to stop.

They were located by running `scripts/check-device-leakage.py --report`
at the failing head `7a3909187` and diffing the per-file table against
`9ecaf1bb3`, rather than by reading the diff for guards.

| # | site at `7a3909187` | what the guard decided | resolution |
|---|---|---|---|
| 1 | `include/vllm/model_executor/models/dense_nvfp4_gemm.h:768` —
inside `MarlinW4A16Selects` | **nothing** | **removed** |
| 2 | `src/vllm/model_executor/models/nemotron_h_device.cpp:883` —
around `LmHeadNvfp4View` | **nothing** | **removed** |
| 3 | `src/vllm/model_executor/models/nemotron_h_device.cpp:985` —
`DeviceLmHeadD`'s body | `ResidentIn` and a complete
`dense_nvfp4::MarlinDenseResident` | **`DSR-ALLOW(A2-Q2b)`** |

**No baseline was changed.** `AGENTS.md` forbids making a red gate green
by widening an assertion, and a baseline bump is that.
`scripts/device-leakage-baseline.json` is untouched at 32, and the
per-file table is now byte-identical to `origin/main`'s.

**(1) is the case the checker's own message describes.**
`MarlinW4A16Selects` is a SELECTION wearing a build guard, and every
term it reads exists in every build: `MarlinW4A16Enabled()` is declared
above the guarded region, and `vt::OpRegistered` is the op/provider
table's own answer to whether the Marlin arm is realized for a device.
The flag and the registration are one condition, not two —
`CMakeLists.txt`'s single `if(VLLM_CPP_MARLIN)` block adds
`src/vt/cuda/cuda_moe_marlin.cu`, whose file-scope `Registrar` holds the
tree's only `RegisterOp(OpId::kMoeGroupedGemmNvfp4Marlin, …)`, and
defines `VT_MARLIN_NVFP4=1` in that same block. A build without the
macro registers nothing, so the query already resolves false on exactly
the builds the `#ifdef` excluded. This is the call
`nemotron_h_device.cpp`'s `moe_on_device` selection had already made, in
a comment that says so.

**(2) was measured rather than reasoned.** `LmHeadNvfp4View` names
nothing the Marlin build adds — `Nvfp4Weight` comes from
`qwen3_5_weights.h` and `OwnedBytes` from the loader, both
unconditional, and no `vt::cuda::` symbol appears in it. The claim that
its external linkage at `namespace vllm` scope is what makes an unused
definition harmless where its only call site is compiled out is proven
by the mutation that removes that property: adding `static` turns the
same CPU compile RED at `rc 1`, `error: 'vllm::Nvfp4Weight
vllm::LmHeadNvfp4View(...)' defined but not used
[-Werror=unused-function]`. The file was restored to an identical sha256
(`9719ea70…`) afterwards.

**(3) is TYPES-not-behaviour and takes the checker's documented escape
hatch.** `DSR-ALLOW` is not a baseline change: the site is excluded from
the count but COUNTED AND PRINTED on every run, so the exemption is
visible in CI output rather than invisible in the diff.
`DeviceLmHeadD`'s body names two symbols that do not EXIST without the
guarded arena region — the `ResidentIn` template, defined inside it, and
`dense_nvfp4::MarlinDenseResident`, which the header declares
unconditionally and defines only under `VT_MARLIN_NVFP4`, so the
reference cannot bind to an incomplete type. It is the same class and
the same stated reason as the five sibling guards A2-Q2a and A2-P
already carry in this file, and its `#else` refuses by name. The
SELECTION for this arm stays a runtime op-table query.

**Measured on this tree**, a CPU build with `VT_MARLIN_NVFP4` absent
from `build/compile_commands.json` — positive control: 1020 `VLLM_CPP`
hits in the same file, so the grep is not silently wrong — which is the
configuration that exercises both removals, because it is the arm the
deleted `#else` branches used to serve.

| what | before (`7a3909187`) | after | `rc` |
|---|---|---|---|
| `check-device-leakage.py` `vt_ifdef` | 35 | **32** | 1 → **0** |
| `DSR-ALLOW` exemptions in force | 20 | **21** | — |
| `scripts/device-leakage-baseline.json` | 32 | **32, untouched** | — |
| per-file table vs `origin/main` | +1 header, +2 model TU |
**identical** | — |
| `nemotron_h_device.cpp`, `nemotron_h.cpp`, `qwen3_5.cpp` at `-Wall
-Wextra -Werror` | — | compile | **0** |

Each repair is individually load-bearing, proven by reverting it alone
in a scratch worktree and re-running the gate. Every mutation was
verified applied by `git diff --stat` and restored to an identical
sha256, with the unmutated control green immediately before and after.

| mutation | applied | `vt_ifdef` | `rc` | verdict |
|---|---|---|---|---|
| — (control) | — | 32 | 0 | `ratchet holds` |
| **M-B** — restore the guard on `MarlinW4A16Selects` | 4 ins | **33** |
**1** | **RED**, `DSR REGRESSION` |
| **M-C** — restore the guard around `LmHeadNvfp4View` | 2 ins | **33**
| **1** | **RED**, `DSR REGRESSION` |
| **M-D** — delete the `DSR-ALLOW(A2-Q2b)` line | 1 del | **33** | **1**
| **RED**, `DSR REGRESSION` |
| — (control, after restore) | — | 32 | 0 | `ratchet holds` |

The two pre-existing allowlist entries the report also prints,
`deepseek_v4_device.cpp [kcuda] x8` and `platform.cpp [dev_cast] x1`,
are byte-identical at `9ecaf1bb3` and here. This change moves one bucket
and nothing else.

**`docs/USAGE.md` rides in the same commit, because it has to and
because it is true.** Any commit touching `include/vllm/` is
`user_usage` to `scripts/check-doc-checkpoint.py` and owes the surface
in that same commit. `doc-checkpoint range` is already red on this
branch for `1c62d9974` and `8fa900a62`, which an operator decision
covers; a third error of the identical kind would be new damage rather
than inherited, so the header hunk was recommitted with the USAGE edit
beside it. The edit is not written to feed the gate: the NemotronH arms
table enumerates what selects the host `lm_head` projection and omitted
`VT_NVFP4_MARLIN=0`, a user-settable knob that selects an arm and
appears nowhere else in USAGE, and this change is precisely what makes
that knob reach the model's eligibility test in every build rather than
only where the guard compiled it in. `check-doc-checkpoint.py --commit`
is `OK` on that commit, with the still-red `1c62d9974` as the positive
control that the checker is armed.

**`origin/main` moved four times during this repair** — `9ecaf1bb3`,
`f07f96e1c`, `aeba0de6f`, `01854663c` — and each time `git merge-base
--is-ancestor` answered 1, which is the arm that SKIPS both trailer
gates without failing. Each was merged rather than worked around, and
every auto-merged keyed record was resolved by key on every merge: the
branch delta across the merge byte-identical to the branch delta from
the merge base, checked file by file with a positive control on the
comparison itself.

### The three red CPU jobs were inherited, and the merge cleared them

`build-test-cpu` and both `sanitize-cpu` arms failed on this pull
request, all three on `test_nemotron_h_paged_forward` —
[#1371](#1371), which
[#1392](#1392) fixed on `main`
after the review rounds. `sanitize-cpu` is NOT a no-baseline job, so
that had to be triaged by WHICH test failed rather than assumed.

The prediction is verified rather than asserted. With `origin/main`
merged, the same binary is green on this tree with NO overlay: `13 | 13
passed | 0 failed`, `assertions: 3269 | 3269 passed | 0 failed`,
`Status: SUCCESS!`, `run_rc 0` — the same numbers the spec's tables
previously recorded only WITH #1392 applied to the working tree and
reverted. **The `#1392` overlay is therefore retired**; the historical
rows in the spec keep their `overlay` cells, because they describe the
tree they were measured on and rewriting them would make them false.

### `main` brought four red suites with it, and they are attributed both
ways

The third sync carried `4712dac40` (`VT-ACT-ROUND-POLARITY`,
[#1322](#1322) via
[#1347](#1347)), and with it four
red CPU suites this branch does not touch. Both `ctest` runs are on this
box, no overlay of any kind.

| tree | base | `ctest` | result |
|---|---|---|---|
| this row + `9ecaf1bb3` | before `4712dac40` | 567 | **`100% tests
passed, 0 tests failed out of 567`**, `rc 0` |
| this row + `01854663c` | after `4712dac40` | 569 | `rc 8`, **4
failed**: `test_minimax_music3_ar`, `test_ltx2_text_encoder`,
`test_muse_glimmer_text`, `test_muse_glimmer_text_fallback` |

Those four are [#1458](#1458),
filed from another flow before this control was run. Inherited, not
caused, and proven in both directions on the same tree rather than
argued — each mutation verified applied by `git diff --stat` and
restored to an identical sha256:

| control | change | `compile_rc` | `run_rc` (music3 / ltx2 / glimmer) |
verdict |
|---|---|---|---|---|
| **A** | revert THIS row's two source files to their pre-repair
`7a3909187` content | 0 | **1 / 1 / 1** | still RED — **not this row's**
|
| **B** | revert `src/vt/cpu/cpu_ops.cpp` alone to `4712dac40^` | 0 |
**0 / 0 / 0**, `37/37`, `27/27`, `24/24` | GREEN — **`4712dac40` is the
cause** |

Control A answers the attribution question on its own; control B is here
because naming a cause is more useful to the next reader than clearing
oneself. Neither repairs anything, deliberately: #1458 needs
`VT-ACT-ROUND-POLARITY` to decide whether its kernel or four bf16 error
floors that were never re-derived are the wrong side, which is that
row's oracle work rather than a small and clear in-flow fix.

**That window has since closed.**
[#1458](#1458) was fixed on
`main` and closed, and this branch has merged the repair, so the four
are not expected on the current head. The controls stay recorded because
a red that was attributed and then disappeared is still the reason two
`ctest` runs in this description disagree, and deleting the evidence
would leave that unexplained.

### `documentation-checkpoint` is the same two commits, and it is not
new

It went red for the same reason `device-leakage` did: it had never
COMPLETED during review. Its whole failure set is `1c62d9974` and
`8fa900a62`, the two an operator decision covers and which cannot be
repaired in place — `check-doc-checkpoint.py` iterates
`commits_in_range` and judges each commit on its own contents, so a
later commit cannot close either, and discharging them means rewriting
the reviewed base.

The job's three invocations were reproduced locally at this head with
CI's own arguments (`--base` the PR base `63d87805c`, `--head` HEAD):

| invocation | result |
|---|---|
| `check-doc-checkpoint.py` | `rc 1` — **exactly `1c62d9974` and
`8fa900a62`, nothing else** |
| `check-now-current.py` | `rc 0`, `OK: .agents/NOW.md is a current,
in-budget resume digest` |
| `check-role-discipline.py` | `rc 0`, `OK: every change on main arrived
on a task branch` |

**This repair's own commits are clean, and that is not an accident.**
The first draft of the header fix added a THIRD error of the identical
kind, and it is the reason `docs/USAGE.md` rides in the same commit as
the `include/vllm/` hunk rather than in a follow-up: the checker's
obligation is per-commit, so a follow-up cannot discharge it.
`check-doc-checkpoint.py --commit 65ab066` is `OK`, with the still-red
`1c62d9974` as the positive control that the checker is armed against
exactly that class.

### Nothing ran at all for a whole push, and the cause was a conflict
rather than a queue

The push before this one produced **zero check runs** on `2c63cc87a`
while other pull requests were starting normally. `gh pr view` named it:
`mergeable=CONFLICTING`, `mergeStateStatus=DIRTY`. GitHub could not
build `refs/pull/1415/merge`, so no `pull_request` workflow had anything
to run against. A healthy Actions queue plus a branch with zero runs is
the shape a conflict makes, and reading it as a slow queue would have
cost the whole verdict.

The conflict was `docs/STATUS.md` and `docs/USAGE.md`, and neither was
resolved by picking a side, because both had been **relocated** on
`main`. `USAGE.md` is now a 231-line hub whose per-model content moved
under `docs/models/`, and `STATUS.md` is a 27-row surface summary with
no per-model rows at all. So both were taken WHOLE from `main` and this
row's scoped edits re-applied BY KEY at their new home,
`docs/models/nemotron-3-5-lightning.md` — 22 insertions and exactly ONE
deletion there, every unrelated key byte-identical.

One of this row's edits is **dropped rather than re-applied**,
deliberately, and the other was never dropped at all — the claim that
both were is corrected below. The `STATUS.md` Nemotron row is genuinely
gone: merged `main`'s `STATUS.md` is a 98-line surface summary that
enumerates no model at all. `Nemotron` returns 0 hits there, and so do
the positive controls `Kimi` and `Laguna`, while the file plainly greps.
Re-adding it would re-create a row a relocation removed, which is the
duplicate a second relocation makes. The `USAGE.md`
`NemotronHForCausalLM` refusal row is the opposite case, and the earlier
claim that it "no longer exists anywhere under `docs/`" was **wrong**:
`aee6c48d6` (#1491) did not delete that row, it **RELOCATED** it to
`docs/reference/model-loading.md` — a file that did not exist at this
branch's pre-merge base, which is precisely why the earlier pass read it
as gone. It is still on `main`, and this row now updates it.
`docs/FEATURES.md` auto-merged and kept this row's cell — checked, not
assumed. `check-conflict-markers.py` (#1450, new on `main`) reports 0
findings over 3820 tracked text files.

Run locally from the ORIGINAL merge base, `check-doc-checkpoint.py` also
reports `d995c52f0`, `af25bd251` and `995ed1ccd` — each verified an
ancestor of `origin/main`, none in this branch's own range, all arrived
with the merge. CI does not see those, because it resolves `--base` to
the CURRENT merge base: **its whole failure set is `1c62d9974` and
`8fa900a62`**, the two an operator decision covers, and nothing else.
This row's own commits stay clean either way: `check-doc-checkpoint.py
--commit` is `OK` on `65ab06636` and on the merge itself.

### Two public rows still said the head runs on the host, and this
change is what made them false

Following the relocation above to its consequence:
`docs/reference/model-loading.md:188` still read "`lm_head` and FP8
Mamba2 projections run on the host". That sentence was true before this
row and is false after it, which is the test for whose obligation it is.
Left alone, the tree would land with
`docs/models/nemotron-3-5-lightning.md` calling the head a device arm
and this row calling it a host arm, on the same merge commit.

Only the clause this row falsified moves. `lm_head` reaches the device
**on the paged forward** and is carried as implemented-and-unmeasured,
matching what the model recipe and the spec's `## Now` already say. The
FP8 Mamba2 projections are a **different unit** (A2-Q1, #1289, still
held DRAFT) and still run on the host, so that clause is kept and only
re-worded to stop sharing a verb with the head. The pending-token-gate
and no-GGUF clauses are untouched, because both are still accurate.

Sweeping by CLAIM rather than by file, at the MERGED tree, found a
**second** site the scoped review did not reach, because it is not a
refusal row. The checkpoint registry at `docs/USAGE.md:230` lists
per-checkpoint arms, and its "Supported arms" cell read "host FP8 Mamba2
and NVFP4 head". That "NVFP4 head" is this same `lm_head`, so the same
test applies and the same one clause moves. That cell is the surface the
"say which weights, and from where" rule owns, so a supported-arms cell
putting the head on the host is exactly the contradiction this finding
is about.

The sweep carried its own controls, because a null grep only proves the
terms wrong. Every `docs/**.md` and `README.md` line asserting a host
placement for a head or for logits is 11 hits, 2 of them the sites
repaired here (positive control); the same pipeline with a nonsense term
returns 0 (negative control). The other 9 were read rather than
pattern-matched, and none is stale: `BENCHMARKS.md:16` is this row's own
host-re-expansion ATTRIBUTION and is what motivates the device arm;
`BENCHMARKS.md:276` is Laguna GB10 ATS weight residency;
`ENVIRONMENT.md:144,187,212` are Qwen3.5/Qwen3.6 levers; `server.md:88`
is the CPU-only `prompt_logprobs` full-logits route on the shared
runner, not this model; `model-loading.md:144` is the FP8 scalar-scale
guard; and two are historical `docs/superpowers/plans/` documents, which
are not projections.

**No gate can catch either repair.** `check-doc-checkpoint.py` asks
whether SOME `docs/USAGE.md` edit accompanied a commit of the changed
class. It never asks whether a sentence inside it is still true.

### Every check has COMPLETED on the current head, and the remaining
reds are all inherited

`ci` run `32537115264` on `99f9f672a`: **25 checks, 0 pending** — 14
pass, 6 fail, 5 skipped by design on a pull request (`attest`,
`baseline-summary`, `manifest`, `promote`, `publish`).

| check | on `b7d89b43e` | on `99f9f672a` | whose |
|---|---|---|---|
| **`device-leakage`** | SUCCESS | **SUCCESS** | **this row's.** The
restructure survives the 38-commit merge at `vt_ifdef` 32, and
`scripts/device-leakage-baseline.json` is byte-identical to `main`, so
the row adds no new allowance |
| `build-newest-gcc` | FAILURE | **SUCCESS** | was **`main`'s**, and it
cleared exactly as predicted once `main` carrying #1581 was merged. CI
builds the MERGE commit, so the red came from the base side; #1565 is
closed |
| `build-test-cpu`, `sanitize-cpu (address,undefined)`, `sanitize-cpu
(thread)` | SUCCESS | **FAILURE** | **`main`'s**,
[#1608](#1608) and
[#1602](#1602) — see below |
| `build-test-vulkan`, `build-test-cpu-arm64`, `cuda-fat-build`,
`cuda-arch-features`, `vulkan-spirv-freshness`, `agent-record`,
`pr-size`, `commit-protocol-tag`, `last-gated-commit`, `plan`, `verify`
x2 | SUCCESS | SUCCESS | — |
| `documentation-checkpoint` | FAILURE | FAILURE | `1c62d9974` +
`8fa900a62` ONLY — the two an operator decision covers, verified against
the job log rather than assumed |
| `windows-msvc-cpu`, `windows-msvc-vulkan` | FAILURE | FAILURE | the
two no-baseline PR-only jobs,
[#584](#584) |

The three CPU legs all fail on **`282 - test_runner`**, and the failure
is inherited rather than this row's. The failing case, `runner:
initialize_kv_cache refuses a non-multiple-of-16 block size`, has **0**
occurrences at `b7d89b43e` — the head where all three legs were SUCCESS
— and **1** at the merge base `5453e571d`. It was introduced by
`e2a9e035d` (#1273), which **is** among the 38 commits the merge brought
in, and this row's range touches **0** files in that test (control: it
does touch the two `tests/vllm/models/test_nemotron_h_*.cpp` files, so
the query discriminates).

It is also **not a data race**, which matters because `sanitize-cpu
(thread)` is the lane that would expose one. All three legs report the
SAME deterministic assertion — `test_runner.cpp:1557`, `20 test cases /
19 passed / 1 failed`, `544 assertions / 543 passed / 1 failed` — and
**0** sanitizer findings in any of them (`WARNING: ThreadSanitizer` = 0,
`ERROR: AddressSanitizer` / `runtime error:` = 0). A race cannot make a
non-sanitized `build-test-cpu` fail identically. The assertion expects
`"Block size must be a multiple of 16"` but the attention registry
refuses first, with `"No valid attention backend for device type 0 from
{CPU_ATTN: [block_size not supported], FLASH_ATTN: [block_size not
supported]}"` — which is precisely what #1602 describes. Nothing is
suppressed here and nothing is pragma'd, because there is nothing of
this row's to suppress.

### Gate table

| leg | state |
|---|---|
| seam extension, device arm, production wiring | DONE; built on BOTH
arms, `-Werror`, 0 warnings |
| the CPU-reachable half, gated through a real `GPUModelRunner` | DONE
and RUN, with M1/M2 red-first |
| routing allowlist narrowed,
[#1410](#1410) filed | DONE |
| synthetic device `lm_head` numeric gate | COMPILES; CUDA-only; **NEVER
RUN** |
| `nvcc` build of the Marlin kernel, real-checkpoint numeric leg, token
identity, reachability deletion mutation, the `VT_NVFP4_MARLIN=0`
fallback red | PENDING a `dgx:gpu0` window, job queued |

A reviewer should treat every CUDA leg as unrun. The spec's `## 6. Now`
carries the same tables, so the pending state lives with the row rather
than only in this description.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]

---------

Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CPU has NO attention backend when FLASH_ATTN declines a head_size: 11 of 13 NemotronH paged cases THROW on main

2 participants